/**************************************** 
* Lista.h
* Declaración de la Clase Lista
****************************************/
#ifndef __LISTA_H__
#define __LISTA_H__

#ifndef NULL
#define NULL 0
#endif
#include "IteradorLista.h"
#include "Comparador.h"
#include "ErrorIteracionException.h"
#include "OperacionInvalidaException.h"
#include "NodoLista.h"

template <class T>
class Lista {
	private:
		NodoLista<T>* primero;
		NodoLista<T>* ultimo;
		Comparador<T>* comparador;
		int numeroNodos;
	public:
		/* pre: -
		 * post: lista inicializada vacia sin comparador (esto inhabilita ciertas
		 * funcionalidades; para hablilitarlas usar setComparador)
		 */
		Lista(){
			this->primero=NULL;
			this->ultimo=NULL;
			this->comparador=NULL;
			this->numeroNodos=0;
		}
		
		/*pre: comparador existente
		  post: lista inicializada vacia con el comparador pasado
		 */
		Lista(Comparador<T>* comp){
			this->primero=NULL;
			this->comparador=comp;
			this->numeroNodos=0;
		}

		/*pre: comparador existente
		  post: comparador asignado a la lista cambiado por el pasado
		 */
		void setComparador(Comparador<T>* comp){
			this->comparador=comp;
		}

		/*pre: -
		  post: elemento agregado en la ultima posición de la lista
		 */
		virtual void agregar(T* dato){
			NodoLista<T>* nuevo = new NodoLista<T>(dato);
			if (!this->estaVacia()) {
				this->ultimo->setSiguiente(nuevo);
			}
			if(this->estaVacia()) this->primero=nuevo;
			this->ultimo=nuevo;
			this->numeroNodos++;
		}
		
		/*pre: -
		  post: retorna un iterador posicionado al comienzo de la lista
		*/
		IteradorLista<T> iterador(){
			IteradorLista<T> it(this);
			return it;
		}
		
		/*pre: -
		  post: devuelve true si la lista esta vacia y false si no lo esta. 
		*/
		bool estaVacia() const{
			return (this->primero == NULL);
		}
		/*pre: -
		  post: devuelve el tamanio de la lista. 
		*/
		int tamanio() const{
			return this->numeroNodos;
		}

		/*pre: comparador != NULL
		 *post: retorna un iterador posicionado en el elemento buscado si este existe;
		 *		si no existe, retorna un iterador al final de la lista. La determinacion
		 *		de la igualdad se realiza utilizando el comparador asignado a la lista.
		 *Arroja: OperacionInvalidaException (Si no hay comparador).
		 */
		T* buscar(T* dato){
			if(comparador!=NULL){
				IteradorLista<T> it = this->iterador();
				while(it.tieneSiguiente()){
					T* aComparar=it.siguiente();
					if(comparador->igualdad(aComparar,dato)) return aComparar;
				}
			}
			else throw OperacionInvalidaException("La operacion \"buscar\" requiere un comparador");
			return NULL;
		}

		/* pre: la lista tiene un comparador asignado; lista no vacia; dato esta en la lista
		 * post: remueve dato de la lista
		 * Arroja: ErrorIteracionException (si dato no pertenece a la lista o si esta vacia)
		 *		   OperacionInvalidaException (si la lista no tiene comparador asignado)
		 */
		void quitar(T* dato){
			if(comparador!=NULL){
				if(!estaVacia()){
					if(comparador->igualdad(this->primero->getDato(),dato)){
						NodoLista<T>* viejoPrimero = this->primero;
						this->setPrimero(viejoPrimero->getSiguiente());
						if(comparador->igualdad(this->ultimo->getDato(),dato))
							ultimo = NULL; // Si el dato a eliminar era el primero
										   // y el ultimo elemento a la vez, entonces
										   // quedan primero y ultimo en valor nulo.
						delete viejoPrimero;
						this->numeroNodos--;
					}
					else{
						bool finalizado = false;
						NodoLista<T>* anterior = this->primero;
						NodoLista<T>* actual = this->primero->getSiguiente();
						while(actual!=NULL && !finalizado){
							NodoLista<T>* sig = actual->getSiguiente();
							if(comparador->igualdad(actual->getDato(),dato)){
								anterior->setSiguiente(sig);
								if(comparador->igualdad(this->ultimo->getDato(),actual->getDato()))
									ultimo=anterior;
								delete actual;
								finalizado = true;
								this->numeroNodos--;
							}
							else{
								anterior=anterior->getSiguiente();
								actual=actual->getSiguiente();
							}
						}
						if(!finalizado){
							throw ErrorIteracionException("Se solicitó eliminación de un elemento inexistente.");
						}
					}
				}else throw ErrorIteracionException("Se solicitó eliminación de un dato en una lista vacía.");
			}
			else throw OperacionInvalidaException("La operacion \"quitar\" requiere un comparador");
		}



		/* pre: -
		 * post: libera recursos asociados a la instancia.
		 */
		virtual ~Lista(){
			NodoLista<T>* actual = this->primero;
			while(actual!=NULL){
				NodoLista<T>* sig = actual->getSiguiente();
				delete actual;
				actual = sig;
			}
			if(this->comparador!=NULL) 
				delete this->comparador;	
 		}		

		/* pre: -
		   post: cambia el primer nodo de la lista por el pasado por parametro
	    */
		void setPrimero(NodoLista<T>* nodo){
			this->primero=nodo;
		}
	
		/* pre: -
		 * post: retorna un puntero al primer nodo de la lista
		 */
		NodoLista<T>* getPrimero(){
			return this->primero;
		}
			
};

#endif
